package top.guoziyang.mydb.backend.common;

import java.util.HashMap;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import top.guoziyang.mydb.common.Error;

/**
 * AbstractCache 实现了一个引用计数策略的缓存
 *  引用计数缓存框架和共享内存数组
 */
public abstract class AbstractCache<T> {
    private HashMap<Long, T> cache;                     // 缓存中实际缓存的数据
    private HashMap<Long, Integer> references;          // 元素的引用个数
    private HashMap<Long, Boolean> getting;             // 正在从数据源获取某资源的线程

    private int maxResource;                            // 缓存的最大缓存资源数（数据页的数量）
    private int count = 0;                              // 缓存中元素的个数
    private Lock lock;

    public AbstractCache(int maxResource) {
        this.maxResource = maxResource;
        cache = new HashMap<>();
        references = new HashMap<>();
        getting = new HashMap<>();
        lock = new ReentrantLock();
    }

    // 从缓存中获取资源
    protected T get(long key) throws Exception {
        // 无限循环尝试从缓存中获取资源（如果资源被其他线程获取，多次尝试，直到成功为止）
        while(true) {
            // 加锁，确保同一时刻只有一个线程访问共享的缓存资源，避免并发问题。
            lock.lock();
            // 判断是否有其他线程正在从 数据源 中获取该资源，如果有，就过会再来看看。
            if(getting.containsKey(key)) {
                // 请求的资源正在被其他线程获取,释放锁，确保其他线程能够获取到锁并对共享资源进行读取和更新。
                lock.unlock();
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    continue;
                }
                continue;
            }
            // 如果该资源已经在缓存中，直接返回该资源，并增加引用计数。
            // 引用计数用于资源管理，比如在确定资源可以从缓存中移除之前，必须确保引用计数为0。
            if(cache.containsKey(key)) {
                // 资源在缓存中，直接返回
                T obj = cache.get(key);
                references.put(key, references.get(key) + 1);
                lock.unlock();
                return obj;
            }

            // 资源不在缓存，尝试从数据源获取该资源
            // count== maxResource表示缓存已经达到最大容量，不能再加载资源
            if(maxResource > 0 && count == maxResource) {
                lock.unlock();
                throw Error.CacheFullException;
            }
            // 否则，缓存未满，可以加载该资源到缓存中
            count ++;  // 缓存中的资源数+1
            getting.put(key, true);  // 设置当前资源状态为正在被加载
            lock.unlock(); //释放锁
            break;
        }

        // 开始执行资源加载逻辑
        T obj = null;
        try {
            obj = getForCache(key);
        } catch(Exception e) {
            // 如果加载失败,重新加锁、减少计数 (count--)、移除 getting 中的 key 表示加载失败，然后抛出异常。
            lock.lock();
            count --;
            getting.remove(key);
            lock.unlock();
            throw e;
        }
        // 加载完成后更新缓存：
        lock.lock();  //加锁 lock.lock()以保证对缓存的操作是线程安全的
        getting.remove(key); //移除 getting 中的 key，表示资源加载完成
        cache.put(key, obj); //将新加载的资源放入缓存 (cache.put(key, obj))
        references.put(key, 1); // 初始化该资源的引用计数为 1 (references.put(key, 1))
        lock.unlock(); // 释放锁
        
        return obj;
    }

    /**
     * 释放一个缓存,key可以是uid或者pgno
     */
    protected void release(long key) {
        lock.lock();
        try {
            // 获取当前资源的引用计数，并将其减一赋值给ref。
            int ref = references.get(key)-1;
            // 如果释放该资源后引用计数为0，表示该资源不再被使用，可以落盘,并且删除缓存中所有相关的结构
            if(ref == 0) {
                T obj = cache.get(key);
                releaseForCache(obj); // 资源被释放时进行落盘
                references.remove(key); // 从引用计数记录中移除该资源。
                cache.remove(key);  // 从缓存中移除该资源
                count --; // 缓存中的资源数-1
            } else {
                // 如果引用计数不为0，则仅更新引用计数。
                references.put(key, ref);
            }
        } finally {
            lock.unlock();
        }
    }

    /**
     * 关闭缓存，写回所有资源
     */
    protected void close() {
        lock.lock();
        try {
            // 获取缓存中的所有 key 集合
            Set<Long> keys = cache.keySet();
            for (long key : keys) {
                T obj = cache.get(key);
                releaseForCache(obj);
                references.remove(key);
                cache.remove(key);
            }
        } finally {
            lock.unlock();
        }
    }


    /**
     * 当资源不在缓存时的获取行为
     */
    protected abstract T getForCache(long key) throws Exception;
    /**
     * 当资源被驱逐时的写回行为
     */
    protected abstract void releaseForCache(T obj);
}
